package fr.utbm.to52.android_bibtex.model.spec;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import my.javax.swing.event.EventListenerList;

public class EntryDefinitions implements EntryDefinitionListener {

	private static final EntryDefinitions INSTANCE = new EntryDefinitions();

	/** key: {@link EntryDefinition#getName()}.toLowerCase() */
	private final Map<String, EntryDefinition> definitions = new HashMap<String, EntryDefinition>();

	private final EventListenerList listeners = new EventListenerList();

	public static EntryDefinitions getInstance() {
		return INSTANCE;
	}

	// Singleton!
	private EntryDefinitions() {
	}

	/**
	 * 
	 * @return a read-only view of the inner map.
	 */
	public Map<String, EntryDefinition> getDefinitions() {
		// Don't return a direct reference, because that would allow uncontrolled adds/removes on it
		return Collections.unmodifiableMap(this.definitions);
	}

	public EntryDefinition getDefinition(String name) {
		return this.definitions.get(name.toLowerCase());
	}

	public boolean containsDefinition(String name) {
		return this.definitions.containsKey(name.toLowerCase());
	}

	public boolean addDefinition(EntryDefinition d) {
		assert d!=null;
		assert d.getName()!=null && !d.getName().isEmpty();

		String lowerCaseName = d.getName().toLowerCase();
		if (!this.definitions.containsKey(lowerCaseName)) {
			this.definitions.put(lowerCaseName, d);
			d.addEntryDefinitionListener(this);
			fireDefinitionAdded(d);
			return true;
		}
		return false;
	}

	public boolean removeDefinition(String name) {
		assert name!=null;

		String lowerCaseName = name.toLowerCase();
		EntryDefinition d = this.definitions.get(lowerCaseName);
		if (d!=null) {
			d.removeEntryDefinitionListener(this);
			this.definitions.remove(lowerCaseName);
			fireDefinitionRemoved(d);
			return true;
		}
		return false;
	}

	public void addEntryDefinitionsListener(EntryDefinitionsListener l) {
		assert l!=null;

		this.listeners.add(EntryDefinitionsListener.class, l);
	}

	public void removeEntryDefinitionsListener(EntryDefinitionsListener l) {
		assert l!=null;

		this.listeners.remove(EntryDefinitionsListener.class, l);
	}

	public EntryDefinitionsListener[] getEntryDefinitionsListeners() {
		return this.listeners.getListeners(EntryDefinitionsListener.class);
	}

	protected void fireDefinitionAdded(EntryDefinition d) {
		assert d!=null;

		EntryDefinitionsEvent event = new EntryDefinitionsEvent(d);
		for (EntryDefinitionsListener listener : getEntryDefinitionsListeners()) {
			listener.definitionAdded(event);
		}
	}

	protected void fireDefinitionRemoved(EntryDefinition d) {
		assert d!=null;

		EntryDefinitionsEvent event = new EntryDefinitionsEvent(d);
		for (EntryDefinitionsListener listener : getEntryDefinitionsListeners()) {
			listener.definitionRemoved(event);
		}
	}

	protected void fireDefinitionChanged(EntryDefinitionEvent changeEvent) {
		assert changeEvent!=null;

		EntryDefinitionsEvent event = new EntryDefinitionsEvent(changeEvent);
		for (EntryDefinitionsListener listener : getEntryDefinitionsListeners()) {
			listener.definitionChanged(event);
		}
	}

	@Override
	public void entryDefinitionChanged(EntryDefinitionEvent event) {
		EntryDefinition ed = event.getSource();

		assert this.definitions.containsKey(ed.getName().toLowerCase());

		if (EntryDefinition.FIELD_SPECIFICATIONS_PROPERTY_NAME.equals(event.getPropertyName())) {
			// TODO handle entry definition's field specifications change
		}

		fireDefinitionChanged(event);
	}

}
